# encoding:utf8

import traceback
from copy import copy
import requests
import os
from urllib3 import encode_multipart_formdata
import time
import hmac
import hashlib
import urllib
import base64
from typing import List
recive = List[str]

class WeWorkAPI():
    """
    参考文档:https://work.weixin.qq.com/api/doc/90000/90136/91770#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%BE%A4%E6%9C%BA%E5%99%A8%E4%BA%BA
    """

    def __init__(self, key):
        self.key = key
        self.webhook = f"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={key}"
        self.header = {"Content-Type": "application/json"}

    def __str__(self):
        return f"webhook is {self.webhook}"

    def client_conn(self, data):
        try:
            response = requests.post(self.webhook, json=data, headers=self.header).json()
            return response
        except Exception:
            raise Exception(traceback.format_exc())

    # 发送文本类型信息
    def send_text_msg(self, content: str, mentioned_list: list = [],
                      mentioned_mobile_list: list = []) -> dict:
        """

        :param msgtype:消息类型，此时固定为text
        :param content:文本内容，最长不超过2048个字节，必须是utf8编码
        :param mentioned_list:userid的列表，提醒群中的指定成员(@某个成员)，@all表示提醒所有人，如果开发者获取不到userid，可以使用mentioned_mobile_list
        :param mentioned_mobile_list:手机号列表，提醒手机号对应的群成员(@某个成员)，@all表示提醒所有人
        :return:

        example:
        {
            "msgtype": "text",
            "text": {
                "content": "广州今日天气：29度，大部分多云，降雨概率：60%",
                "mentioned_list":["wangqing","@all"],
                "mentioned_mobile_list":["13800001111","@all"]
            }
        }
        """
        mentioned_lists = mentioned_list if isinstance(mentioned_list, list) and mentioned_list else []
        mentioned_mobile_lists = mentioned_mobile_list if isinstance(mentioned_list,
                                                                     list) and mentioned_mobile_list else []
        params = {
            "text": {
                "content": content,
                "mentioned_list": mentioned_lists,
                "mentioned_mobile_list": mentioned_mobile_lists
            },
            "msgtype": "text"
        }
        return self.client_conn(params)

    # 发送markdown类型消息
    def send_markdwon_msg(self, content: str) -> dict:
        """

        :param content:markdown内容，最长不超过4096个字节，必须是utf8编码
        :return:

        example:
        {
            "msgtype": "markdown",
            "markdown": {
                "content": "实时新增用户反馈<font color=\"warning\">132例</font>，请相关同事注意。\n
                 >类型:<font color=\"comment\">用户反馈</font>
                 >普通用户反馈:<font color=\"comment\">117例</font>
                 >VIP用户反馈:<font color=\"comment\">15例</font>"
            }
        }
        """
        params = {
            "msgtype": "markdown",
            "markdown": {
                "content": content,
            }
        }
        return self.client_conn(params)

    # 发送图片类型消息
    def send_image_msg(self, base64_value: str, md5_value: str) -> dict:
        """

        :param base64_value:图片内容的base64编码
        :param md5_value:图片内容（base64编码前）的md5值
        :return:
        注：图片（base64编码前）最大不能超过2M，支持JPG,PNG格式

        example:
        {
            "msgtype": "image",
            "image": {
                "base64": "DATA",
                "md5": "MD5"
            }
        }
        """
        params = {
            "msgtype": "image",
            "image": {
                "base64": base64_value,
                "md5": md5_value
            }
        }

        return self.client_conn(params)

    # 发送图文类型消息
    def send_link_msg(self, title: [], url: [], description: list = [], picurl: list = []) -> dict:
        """

        :param title:标题，不超过128个字节，超过会自动截断
        :param url:点击后跳转的链接。
        :param description:描述，不超过512个字节，超过会自动截断
        :param picurl:图文消息的图片链接，支持JPG、PNG格式，较好的效果为大图 1068*455，小图150*150。

        :return:

        example:
        {
            "msgtype": "news",
            "news": {
               "articles" : [
                   {
                       "title" : "中秋节礼品领取",
                       "description" : "今年中秋节公司有豪礼相送",
                       "url" : "www.qq.com",
                       "picurl" : "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"
                   }
                ]
            }
        }
        """

        params = {
            "msgtype": "news",
            "news": {
                "articles": [

                ]
            }
        }
        if len(title) == len(url) and len(description) and len(picurl):
            for t, u, d, p in zip(title, url, description, picurl):
                article = {
                    "title": t,
                    "description": d,
                    "url": u,
                    "picurl": p
                }
                params['news']['articles'].append(article)

            return self.client_conn(params)
        else:
            raise Exception("检查title,url,description,picurl 的参数长度是否一致")

    # 发送文件类型
    def send_file_msg(self, media_id: str) -> dict:
        """

        :param media_id: 文件id，通过下文的文件上传接口获取
        :return:

        example:
        {
            "msgtype": "file",
            "file": {
                 "media_id": "3a8asd892asd8asd"
            }
        }
        """

        params = {
            "msgtype": "file",
            "file": {
                "media_id": media_id
            }
        }

        return self.client_conn(params)

    # 获取文件media_id
    def get_file_media_id(self, file_path, file_type: str):
        """

        :param file_path:  文件路径
        :param file_type:   媒体文件类型，分别有图片（image）、语音（voice）、视频（video），普通文件（file）
        :return:
        所有文件size必须大于5个字节
        图片（image）：2MB，支持JPG,PNG格式
        语音（voice） ：2MB，播放长度不超过60s，仅支持AMR格式
        视频（video） ：10MB，支持MP4格式
        普通文件（file）：20MB
        """
        if file_type in ['image', 'voice', 'video', 'file']:
            file_type = file_type
        else:
            raise Exception(
                "文件类型异常,请参考https://work.weixin.qq.com/api/doc/90000/90136/91770#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E7%BE%A4%E6%9C%BA%E5%99%A8%E4%BA%BA")
        file_upload_url = f"https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={self.key}&type={file_type}"
        file_name = file_path.split("/")[-1]
        with open(file_path, 'rb') as f:
            length = os.path.getsize(file_path)
            data = f.read()
        headers = {"Content-Type": "application/octet-stream"}
        params = {
            "filename": file_name,
            "filelength": length,
        }
        file_data = copy(params)
        file_data['file'] = (file_path.split('/')[-1:][0], data)
        encode_data = encode_multipart_formdata(file_data)
        file_data = encode_data[0]
        headers['Content-Type'] = encode_data[1]
        response = requests.post(file_upload_url, data=file_data,
                                 headers={"Content-Type": "application/octet-stream"}).json()
        return response['media_id']

    def send_msg(self, send_type, **kwargs):
        try:
            _models = ["send_text_msg", "send_markdwon_msg", "send_image_msg", "send_link_msg",
                       "send_file_msg",
                       "get_file_media_id"]
            getattr(self, send_type)(**kwargs)
        except Exception:
            raise Exception(traceback.format_exc())

class DingTalkApi():
    """
    Document :https://developers.dingtalk.com/document/app/custom-robot-access
     _____   _   __   _   _____   _____       ___   _       _   _
    |  _  \ | | |  \ | | /  ___| |_   _|     /   | | |     | | / /
    | | | | | | |   \| | | |       | |      / /| | | |     | |/ /
    | | | | | | | |\   | | |  _    | |     / / | | | |     | |\ \
    | |_| | | | | | \  | | |_| |   | |    / /  | | | |___  | | \ \
    |_____/ |_| |_|  \_| \_____/   |_|   /_/   |_| |_____| |_|  \_\
    """

    def __init__(self, access_token: str, secret: str = None):
        self.webhook = f"https://oapi.dingtalk.com/robot/send?access_token={access_token}"
        if secret:
            timestamp, sign = self._signature(secret)
            self.webhook = f"{self.webhook}&timestamp={timestamp}&sign={sign}"

    def __str__(self):
        return f"webhook is {self.webhook}"

    @staticmethod
    def _signature(secret):
        timestamp = str(round(time.time() * 1000))
        secret_enc = secret.encode('utf-8')
        string_to_sign = f'{timestamp}\n{secret}'
        string_to_sign_enc = string_to_sign.encode('utf-8')
        hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
        sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
        return timestamp, sign

    def client_conn(self, data):
        try:
            response = requests.post(self.webhook, json=data).json()
            return response
        except Exception:
            raise Exception(traceback.format_exc())

    def send_text_msg(self, content: str, atMobiles: list = None, atUserIds: list = None,
                      isAtAll: bool = False) -> dict:
        """
        发送文本类型信息

        :param msgtype:消息类型，此时固定为：text。
        :param content:	消息内容。
        :param atMobiles:被@人的手机号。注意 在content里添加@人的手机号。
        :param atUserIds:被@人的用户userid。
        :param isAtAll:是否@所有人。默认位False
        :return:

        example:
        {
            "at": {
                "atMobiles":[
                    "13888888888"
                ],
                "atUserIds":[
                    "user123"
                ],
                "isAtAll": false
            },
            "text": {
                "content":"我就是我, @13888888888 是不一样的烟火"
            },
            "msgtype":"text"
        }
        """
        atMobiles = atMobiles if isinstance(atMobiles, list) and atMobiles else []
        atUserIds = atUserIds if isinstance(atUserIds, list) and atUserIds else []

        params = {
            "at": {
                "atMobiles": atMobiles,
                "atUserIds": atUserIds,
                "isAtAll": isAtAll
            },
            "text": {
                "content": content
            },
            "msgtype": "text"
        }
        return self.client_conn(params)

    def send_link_msg(self, title: str, text: str, messageUrl: str, picUrl: str = "") -> dict:
        """
        发送链接类型消息

        :param title: 消息标题
        :param text: 消息内容,如果太长只会部分展示
        :param messageUrl: 点击消息跳转的url
        :param picUrl: 图片Url
        :return:

        example:
        {
            "msgtype": "link",
            "link": {
                "text": "这个即将发布的新版本，创始人xx称它为红树林。而在此之前，每当面临重大升级，产品经理们都会取一个应景的代号，这一次，为什么是红树林",
                "title": "时代的火车向前开",
                "picUrl": "",
                "messageUrl": "https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI"
                }
        }
        """
        if title and text and messageUrl:
            params = {
                "msgtype": "link",
                "link": {
                    "text": text,
                    "title": title,
                    "picUrl": picUrl,
                    "messageUrl": messageUrl
                }
            }
            return self.client_conn(params)
        else:
            raise Exception("title | text | messageUrl 不能为空")

    def send_feedcard_msg(self, title: list, messageURL: list, picURL: list) -> dict:
        """
        发送feedcard类型消息

        :param title:单条信息文本 ["时代的火车向前开1",时代的火车向前开2]
        :param messageURL:点击单条信息到跳转链接 ["https://www.dingtalk.com/","https://www.dingtalk.com/"]
        :param picURL:单条信息后面图片的URL ["https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png","https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"]
        :return:

        example:
        {
            "msgtype":"feedCard",
            "feedCard": {
                "links": [
                   {
                        "title": "时代的火车向前开1",
                        "messageURL": "https://www.dingtalk.com/",
                        "picURL": "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
                    },
                    {
                        "title": "时代的火车向前开2",
                        "messageURL": "https://www.dingtalk.com/",
                        "picURL": "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
                    }
                ]
            }
        }
        """

        if title and messageURL and picURL:
            params = {
                "msgtype": "feedCard",
                "feedCard": {
                    "links": []
                }
            }
            for t, m, p in zip(title, messageURL, picURL):
                params["feedCard"]['links'].append({"title": t, "messageURL": m, "picURL": p})
            return self.client_conn(params)
        else:
            raise Exception("title | messageURL | picURL 不能为空")

    def send_markdwon_msg(self, title: str, text: str, atMobiles: list = None, atUserIds: list = None,
                          isAtAll: bool = False) -> dict:
        """
        发送markdown类型消息

        :param title: 首屏会话透出的展示内容。
        :param text:markdown格式的消息。
        :param atMobiles:被@人的手机号。
        :param atUserIds:被@人的用户userid。
        :param isAtAll:是否@所有人。
        :return:

        example:
        {
            "msgtype": "markdown",
            "markdown": {
                "title":"杭州天气",
                "text": "#### 杭州天气 @150XXXXXXXX \n > 9度，西北风1级，空气良89，相对温度73%\n > ![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png)\n > ###### 10点20分发布 [天气](https://www.dingtalk.com) \n"
            },
            "at": {
                "atMobiles": [
                    "150XXXXXXXX"
                ],
                "atUserIds": [
                    "user123"
                ],
                "isAtAll": false
            }
        }
        """
        atMobiles = atMobiles if isinstance(atMobiles, list) and atMobiles else []
        atUserIds = atUserIds if isinstance(atUserIds, list) and atUserIds else []
        params = {
            "msgtype": "markdown",
            "markdown": {
                "title": title,
                "text": text
            },
            "at": {
                "atMobiles": atMobiles,
                "atUserIds": atUserIds,
                "isAtAll": isAtAll
            }
        }
        return self.client_conn(params)

    def send_actioncard_msg(self, title: str, text: str, title_child: list, actionURL: list,
                            btnOrientation: str = "0") -> dict:
        """
        发送整体卡片

        :param title: 首屏会话透出的展示内容。
        :param text:markdown格式的消息。
        :param btns:按钮
        :param title_child:按钮标题
        :param actionURL:点击按钮触发的URL
        :param btnOrientation:0：按钮竖直排列  1：按钮横向排列 默认为0
        :return:

        example:
        {
            "msgtype": "actionCard",
            "actionCard": {
                "title": "我 20 年前想打造一间苹果咖啡厅，而它正是 Apple Store 的前身",
                "text": "![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png) \n\n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化，而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
                "btnOrientation": "0",
                "btns": [
                    {
                        "title": "内容不错",
                        "actionURL": "https://www.dingtalk.com/"
                    },
                    {
                        "title": "不感兴趣",
                        "actionURL": "https://www.dingtalk.com/"
                    }
                ]
            }
        }
        """
        params = {
            "msgtype": "actionCard",
            "actionCard": {
                "title": title,
                "text": text,
                "btnOrientation": btnOrientation,
                "btns": []
            }
        }
        for t, a in zip(title_child, actionURL):
            params["actionCard"]['btns'].append({"title": t, "actionURL": a})
        return self.client_conn(params)

    def send_whole_actioncard_msg(self, title: str, text: str, singleTitle: str, singleURL: str,
                                  btnOrientation: str = "0") -> dict:
        """
        发送独立卡片

        :param title:首屏会话透出的展示内容。
        :param text:markdown格式的消息。
        :param singleTitle:单个按钮的标题。注意 设置此项和singleURL后，btns无效。
        :param singleURL:点击singleTitle按钮触发的URL。
        :param btnOrientation:0：按钮竖直排列  1：按钮横向排列 默认为0
        :return:

        example:
        {
            "actionCard": {
                "title": "乔布斯 20 年前想打造一间苹果咖啡厅，而它正是 Apple Store 的前身",
                "text": "![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png) \n### 乔布斯 20 年前想打造的苹果咖啡厅Apple Store 的设计正从原来满满的科技感走向生活化，而其生活化的走向其实可以追溯到20        年前苹果一个建立咖啡馆的计划        ",
                "btnOrientation": "0",
                "singleTitle": "阅读全文",
                "singleURL": "https://www.dingtalk.com/"
            },
            "msgtype": "actionCard"
        }
        """
        params = {
            "actionCard": {
                "title": title,
                "text": text,
                "btnOrientation": btnOrientation,
                "singleTitle": singleTitle,
                "singleURL": singleURL
            },
            "msgtype": "actionCard"
        }
        return self.client_conn(params)

    def send_msg(self, send_type, **kwargs):
        try:
            _models = ["send_text_msg", "send_link_msg", "send_feedcard_msg", "send_markdwon_msg",
                       "send_actioncard_msg", "send_whole_actioncard_msg"]
            getattr(self, send_type)(**kwargs)
        except Exception:
            raise Exception(traceback.format_exc())



